angular.module('datetime', [])

/**
 * @ngdoc service
 * @name datetime.DateUtils
 * @description
 *   日期时间相关的工具函数
 */
.factory('DateUtils', function(dateFilter) {

  var DAYTIME = 86400000;

  return {
    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#floorDate
     * @methodOf  datetime.DateUtils
     * @description
     * 设置日期到该日起始点，即 0 时 0 分 0 秒 0 毫秒
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @returns   {Date}    日期
     */
    floorDate: function(date) {
      date = angular.isDate(date) ? date : typeof date !== 'undefined' ? new Date(date) : new Date();
      date.setHours(0);
      date.setMinutes(0);
      date.setSeconds(0);
      date.setMilliseconds(0);
      return date;
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#ceilDate
     * @methodOf  datetime.DateUtils
     * @description
     * 设置日期到该日结束点
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @returns   {Date}    日期
     */
    ceilDate: function(date) {
      date = angular.isDate(date) ? date : typeof date !== 'undefined' ? new Date(date) : new Date();
      date.setTime(+this.floorDate(date) + DAYTIME - 1);
      return date;
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#isToday
     * @methodOf  datetime.DateUtils
     * @description
     * 指定日期是否在今日范围内
     *
     * @param     {Date|String|Number}     date       日期类型，或可转化为日期的值
     * @param     {Date|String|Number}     relative   用于作为对比的日期
     * @returns   {Boolean}    指定日期在今日范围内为 true
     */
    isToday: function(date, relative) {
      date = new Date(date);
      relative = relative ? new Date(relative) : new Date();

      return date >= this.floorDate(relative) &&
        date <= this.ceilDate(relative);
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#isTomorrow
     * @methodOf  datetime.DateUtils
     * @description
     * 指定日期是否在明日范围内
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @param     {Date|String|Number}     relative   用于作为对比的日期
     * @returns   {Boolean}    指定日期在明日范围内为 true
     */
    isTomorrow: function(date, relative) {
      date = new Date(date);
      relative = relative ? new Date(relative) : new Date();

      var tomorrow = new Date(+relative + DAYTIME);

      return date >= this.floorDate(tomorrow) &&
        date <= this.ceilDate(tomorrow);
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#isYesterday
     * @methodOf  datetime.DateUtils
     * @description
     * 指定日期是否在昨日范围内
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @param     {Date|String|Number}     relative   用于作为对比的日期
     * @returns   {Boolean}    指定日期在昨日范围内为 true
     */
    isYesterday: function(date, relative) {
      date = new Date(date);
      relative = relative ? new Date(relative) : new Date();

      var yesterday = new Date(+relative - DAYTIME);

      return date >= this.floorDate(yesterday) &&
        date <= this.ceilDate(yesterday);
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#inThisWeek
     * @methodOf  datetime.DateUtils
     * @description
     * 指定日期是否在本周范围内
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @returns   {Boolean}    指定日期在本周范围内为 true
     */
    inThisWeek: function(date) {
      date = new Date(date);
      var today = new Date();
      var timestamp = date.getTime();

      var weekStartTime = +this.floorDate(today) - today.getDay() * DAYTIME;
      var weekEndTime = +this.ceilDate(today) + (6 - today.getDay()) * DAYTIME;

      return timestamp >= weekStartTime &&
        timestamp <= weekEndTime;
    },

    /**
     * @ngdoc     method
     * @name      datetime.DateUtils#inThisYear
     * @methodOf  datetime.DateUtils
     * @description
     * 指定日期是否在今年范围内
     *
     * @param     {Date|String|Number}     date     日期类型，或可转化为日期的值
     * @returns   {Boolean}    指定日期在今年范围内为 true
     */
    inThisYear: function(date) {
      date = new Date(date);
      var today = new Date();
      return today.getFullYear() === date.getFullYear();
    }
  };
})

/**
 * @ngdoc filter
 * @name datetime.filter:humanizedTime
 * @function
 * @description
 *   计算给出时间与当前时间的差，并以人性化的格式显示
 *   
 * @param   {date|number}  date  日期或时间戳
 * @returns {string}       人性化格式的时间
 */
.filter('humanizedTime', function(dateFilter) {

  return function(date, format) {
    date = +date;
    format = format || 'yyyy-MM-dd';
    var now = +new Date();

    var postfix = now >= date ? 
      ['秒前', '分钟前', '小时前', '天前'] : 
      ['秒后', '分钟后', '小时后', '天后'];

    var fromNow = Math.abs(now - date) / 1000;
    if(fromNow < 60) {
      return Math.floor(fromNow) + postfix[0];
    }

    fromNow = fromNow / 60;
    if(fromNow < 60) {
      return Math.floor(fromNow) + postfix[1];
    }

    fromNow = fromNow / 60;
    if(fromNow < 24) {
      return Math.floor(fromNow) + postfix[2];
    }

    fromNow = fromNow / 24;
    if(fromNow < 7) {
      return Math.floor(fromNow) + postfix[3];
    }

    return dateFilter(date, format);
  }

})

/**
 * @ngdoc filter
 * @name datetime.filter:humanizedDate
 * @function
 * @description
 *   以人性化的格式显示日期
 *   
 * @param   {date|number}  date    日期或时间戳
 * @param   {string}       format  默认的日期格式
 * @returns {string}       人性化格式的时间
 */
.filter('humanizedDate', function(dateFilter) {

  return function(date, format) {
    date = +date;
    format = format || 'yyyy-MM-dd';
    var now = new Date();
    now.setHours(0);
    now.setMinutes(0);
    now.setSeconds(0);
    now.setMilliseconds(0);
    var day = (date - now) / 86400000;

    switch(Math.floor(day)) {
      case -2:
        return '前天';
      case -1:
        return '昨天';
      case 0:
        return '今天';
      case 1:
        return '明天';
      case 2:
        return '后天';
      default:
        return dateFilter(date, format);
    }
  };
})


/**
 * @ngdoc filter
 * @name datetime.filter:humanizedDateTime
 * @function
 * @description
 *   以人性化的格式显示日期
 *   同一天内显示格式如 10:30
 *   昨天、明天分别以文字显示
 *   同一年内显示格式如 01-01
 *   其他显示格式如 2016-01-01
 *   
 * @param   {date|number}  date    日期或时间戳
 * @returns {string}       人性化格式的时间
 */
.filter('humanizedDateTime', function(dateFilter, DateUtils) {

  var defaultFormats = {
    'today': 'HH:mm',
    'yesterday': '昨天',
    'tomorrow': '明天',
    'thisWeek': 'EEE',
    'thisYear': 'MM-dd',
    'default': 'yyyy-MM-dd'
  };

  return function(date, formats) {
    var type = 'default';
    formats = angular.extend({}, defaultFormats, formats);

    if(DateUtils.isToday(date)) {
      type = 'today';
    } else if(DateUtils.isYesterday(date)){
      type = 'yesterday';
    } else if(DateUtils.isTomorrow(date)){
      type = 'tomorrow';
    } else if(DateUtils.inThisWeek(date)) {
      type = 'thisWeek';
    } else if(DateUtils.inThisYear(date)) {
      type = 'thisYear';
    }

    return dateFilter(date, formats[type]);
  };
})

/**
 * @ngdoc filter
 * @name datetime.filter:isToday
 * @function
 * @description
 *   判断一个时间戳是否是今天
 *    
 * @param  {String|Number}  timestampe  时间戳
 * @return {Boolean}        是否今天
 */
.filter('isToday', function(dateFilter) {
  return function(timestamp) {
    return dateFilter(new Date(), 'yyyyMMdd') === dateFilter(timestamp, 'yyyyMMdd');
  }
})

/**
 * @ngdoc filter
 * @name datetime.filter:isAfterThanNow
 * @function
 * @description
 *   时间是否在现在之后
 *     
 * @param   {string|number}       timestamp    时间戳
 * @returns {boolean}             true 表示时间在现在之后
 */
.filter('isAfterThanNow', function() {
  return function(timestamp) {
    return timestamp > +new Date();
  };
})

/**
 * @ngdoc filter
 * @name datetime.filter:isBeforeThanNow
 * @function
 * @description
 *   时间是否在现在之前
 *     
 * @param   {string|number}       timestamp    时间戳
 * @returns {boolean}             true 表示时间在现在之前
 */
.filter('isBeforeThanNow', function() {
  return function(timestamp) {
    return timestamp < +new Date();
  };
})

/**
 * @ngdoc filter
 * @name  datetime.directive:dateStrInput
 * @restrict A
 * @element input[type="date"]
 * @require ngModel
 * @description
 *   使日期控件能接收字符串格式的日期
 *     
 * @param   {string}       dateStrInput    日期格式
 */
.directive('dateStrInput', function(dateFilter) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function($scope, $element, $attrs, $ngModelCtrl) {
      var format = $attrs.dateStrInput || 'yyyy-MM-dd'
      $ngModelCtrl.$parsers.push(function(val) {
        return dateFilter(val, format)
      })
      $ngModelCtrl.$formatters.push(function(val) {
        return new Date(val)
      })
    }
  }
})